/**
 * Copyright 2018 asiainfo Inc.
 **/
package com.myz.app.shiro.session;

import com.myz.app.utils.SerializeUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Collection;

/**
 * CachingSessionDAO extends AbstractSessionDAO
 * 针对自定义的ShiroSession的Redis CRUD操作，通过isChanged标识符，确定是否需要调用Update方法
 * 通过配置securityManager在属性cacheManager查找从缓存中查找Session是否存在，如果找不到才调用下面方法
 * Shiro内部相应的组件（DefaultSecurityManager）会自动检测相应的对象（如Realm）是否实现了CacheManagerAware并自动注入相应的CacheManager。
 *
 * @author maoyz on 2018/6/11
 * @version: v1.0
 */
public class CachingShiroSessionDao extends CachingSessionDAO {

  protected final Logger logger = LoggerFactory.getLogger(CachingShiroSessionDao.class);

  private final String keyPrefix = "shiro_redis_session:";

  private RedisManager redisManager;

  public RedisManager getRedisManager() {
    return redisManager;
  }

  public void setRedisManager(RedisManager redisManager) {
    this.redisManager = redisManager;
  }

  /**
   * 重写CachingSessionDAO中readSession方法，如果Session中没有登陆信息就调用doReadSession方法从Redis中重读
   * session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null 代表没有登录，登录后Shiro会放入该值
   */
  @Override
  public Session readSession(Serializable sessionId) throws UnknownSessionException {
    logger.debug("================== readSession() ==================");
    // 从缓存中获取session
    Session session = getCachedSession(sessionId);
    if (session == null || session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
      // doReadSession()从redis中获取
      session = this.doReadSession(sessionId);
      if (session == null){
        throw new UnknownSessionException("不存在sessionid为:" + sessionId +"的session");
      } else {
        // todo 缓存
        cache(session, session.getId());
      }
    }
    return session;
  }

  /**
   * 2 根据会话ID获取会话
   *
   * @param sessionId
   */
  @Override
  protected Session doReadSession(Serializable sessionId) {
    logger.debug("============ doReadSession() =============");
    if (sessionId == null) {
      logger.error("session id is null");
      return null;
    } else {
      // 从redis中获取sessionId对应session，并反序列化，返回
      // Session session = (Session) SerializeUtils.deserialize(redisManager.get(this.getKey(sessionId)));
      Session session = (Session) SerializeUtils.deserializeFromString(redisManager.get(this.getStringKey(sessionId)));
      return session;
    }
  }


  /**
   * 4 更新会话；如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
   *
   * @param session
   */
  @Override
  protected void doUpdate(Session session) {
    logger.debug("================== doUpdate() ===================");
    if (session instanceof ValidatingSession && !((ValidatingSession)session).isValid()){
      return;
    }

    if (session instanceof ShiroSession){
      
    }
    this.saveSession(session);
  }

  /**
   * 删除会话；当会话过期/会话停止（如用户退出时）会调用
   *
   * @param session
   */
  @Override
  protected void doDelete(Session session) {
    logger.debug("=================== doCreate() ==================");
    if (session != null && session.getId() != null) {
      redisManager.delete(getKey(session.getId()));
    }

  }

  /**
   * 3 如DefaultSessionManager在创建完session后会调用该方法；如保存到关系数据库/文件系统/NoSQL数据库；即可以实现会话的持久化；
   * 返回会话ID；主要此处返回的ID.equals(session.getId())；
   *
   * @param session
   */
  @Override
  protected Serializable doCreate(Session session) {
    logger.debug("================== deCreate() ===================");
    // 创建一个Id并设置给Session
    Serializable sessionId = generateSessionId(session);
    // 分派session_id
    this.assignSessionId(session, sessionId);
    // 保存session，方便读取
    this.saveSession(session);
    return sessionId;
  }

  /**
   * 保存session
   *
   * @param session
   */
  private void saveSession(Session session) {
    // 1 session.getId()获取当前会话的唯一标识
    // byte[] key = getKey(session.getId());
    String key = getStringKey(session.getId());

    // session序列化之后，保存
    // byte[] val = SerializeUtils.serialize(session);
    String val = SerializeUtils.serializeToString(session);
    // 2 设置session过时时间
    session.setTimeout(redisManager.getExpire() * 1000);
    // 3 保存在redis
    redisManager.set(key, val, redisManager.getExpire());
  }

  private byte[] getKey(Serializable sessionId) {
    // 前缀 + session_id
    String key = keyPrefix + sessionId;
    // 转为byte[]
    return key.getBytes();
  }

  private String getStringKey(Serializable sessionId) {
    String key = keyPrefix + sessionId;
    return key;
  }


  /**
   * 获取当前所有活跃用户
   *
   * @return
   */
  @Override
  public Collection<Session> getActiveSessions() {
    logger.debug("=============== getActiveSessions() ================");
    return null;
  }
}
